package com.kaifamiao.utils;

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.logging.Logger;

public class JdbcHelper {

    private static final Logger LOGGER = Logger.getAnonymousLogger();
    private static final String CONNECT = "jdbc.connect" ;
    private static final String PREFIX = "jdbc.connection." ;
    private static final String SUFFIX_DRIVER = ".driver";
    private static final String SUFFIX_URL = ".url";
    private static final String SUFFIX_USERNAME = ".username";
    private static final String SUFFIX_PASSWORD = ".password";
    private static final String CONFIG = "jdbc.properties";
    private static final Properties PROPS = new Properties();

    private static String dbName ; // 被连接的数据库
    private static String driver ; // 驱动类
    private static String url ; // URL
    private static String username ; // 数据库用户名
    private static String password ; // 数据库密码

    // 用来缓存已经创建好的有效连接
    private Connection connection ;

    static {
        // 读取配置文件
        read();
        // 从 PROPS 中获取相应的值
        configure();
    }

    private JdbcHelper(){
    }

    private static void read(){
        try {
            Class<?> c = JdbcHelper.class ;
            // 获取用于读取类路径根目录下指定文件的字节输入流
            InputStream in = c.getResourceAsStream("/" + CONFIG );
            // 加载字节输入流中的内容到 Properties 集合中
            PROPS.load(in);
            in.close();
        } catch ( IOException e ) {
            e.printStackTrace();
        }
    }

    private static void configure(){
        // 首先确定连接哪个数据库
        dbName = PROPS.getProperty( CONNECT );
        driver = PROPS.getProperty( PREFIX + dbName + SUFFIX_DRIVER );
        url = PROPS.getProperty( PREFIX + dbName + SUFFIX_URL );
        username = PROPS.getProperty( PREFIX + dbName + SUFFIX_USERNAME );
        password = PROPS.getProperty( PREFIX + dbName + SUFFIX_PASSWORD );
    }

    public static JdbcHelper getInstance(){
        JdbcHelper h = new JdbcHelper();
        h.load();
        h.connect();
        return h ;
    }

    // 用来加载数据库驱动的方法
    private void load(){
        try {
            Class.forName( driver );
            LOGGER.info( "驱动[" + driver + "]加载成功" );
        } catch (ClassNotFoundException e) {
            LOGGER.info( "驱动[" + driver + "]未找到: " + e.getMessage() );
        }
    }

    /**
     * 检查连接是否无效的方法
     * @return 当连接无效时返回 true
     */
    private boolean invalid(){
        if( connection == null ){
            return true ;
        }

        try {
            return !connection.isValid(0);
        } catch (SQLException e) {
            LOGGER.info( "判断数据库连接是否有效时发生错误:" + e.getMessage() );
        }

        return false ;
    }

    private void connect(){
        if( this.invalid() ) {
            try {
                connection = DriverManager.getConnection( url , username , password );
                LOGGER.info( "已经成连接到" + dbName );
            } catch (SQLException e) {
                LOGGER.info( "创建数据库连接时发生错误: " + e.getMessage() );
            }
        }
    }

    // 根据用户给定的 SQL语句创建 PreparedStatement 对象
    private PreparedStatement prepare( String sql ){
        try {
            PreparedStatement ps = connection.prepareStatement( sql );
            LOGGER.info( "成功创建PreparedStatement" );
            return ps ;
        } catch (SQLException e ) {
            LOGGER.info( "创建PreparedStatement时发生错误: " + e.getMessage() );
        }
        return null ;
    }

    // 为 PreparedStatement 设置参数占位符的值
    private void setParameters( PreparedStatement ps , Object... params ) {
        if( params != null && params.length > 0 ) {
            try {
                for (int i = 0; i < params.length; i++) {
                    ps.setObject(i + 1, params[i]);
                }
                LOGGER.info("成功设置参数占位符的值");
            } catch (SQLException e) {
                LOGGER.info("设置参数占位符取值时发生错误: " + e.getMessage());
            }
        }
    }

    /**
     * 执行DML语句并返回受DML语句影响的记录数
     * @param sql 即被执行的SQL语句(可以带参数占位符，也可以不带参数占位符)
     * @param params 如果sql是带有参数占位符的，则依次传入参数占位符的值
     * @return 返回受DML语句影响的记录数目
     */
    public int execute( String sql , Object... params ) {
        if( sql == null || ( sql = sql.trim() ).isEmpty() ) {
            throw new IllegalArgumentException( "被执行SQL不能为空" );
        }

        // 将整个SQL语句所有英文字母转大写
        sql = sql.toUpperCase() ;

        // 判断SQL语句是否不是DML语句
        if( !sql.startsWith( "INSERT" ) && !sql.startsWith( "UPDATE" ) && !sql.startsWith( "DELETE" )){
            throw new RuntimeException( "对不起，老子只能执行DML语句" );
        }

        // 如果sql语句中包含参数占位符，则必须可变长参数必须有数据传入
        if( sql.indexOf( '?' ) != -1 && ( params == null || params.length == 0 ) ) {
            throw new IllegalArgumentException( "未设置参数占位符的值: " + sql );
        }

        // 将SQL发送给数据库服务器并获取数据库返回的标识(zhi)符
        PreparedStatement ps = this.prepare( sql );

        // 如果sql语句中有参数占位符，设置参数占位符的值
        this.setParameters( ps , params );

        try {
            int count = ps.executeUpdate(); // 这里居然没有处理事务
            LOGGER.info( "DML语句执行成功: " + sql );
            return count ;
        } catch (SQLException e) {
            LOGGER.info( "执行SQL时发生错误: " + e.getMessage() );
        }

        this.release( ps );

        return 0 ;
    }

    /**
     * 执行查询语句并使用给定的RowHandler将结果集中的单行数据包裹成T类型的对象
     * @param sql 即将被执行的SQL语句(可以带参数占位符，也可以不带参数占位符)
     * @param handler 用来将结果集中的单行数据包装成T类型的对象的RowHandler实例
     * @param params 如果sql是带有参数占位符的，则依次传入参数占位符的值
     * @param <T> 类型参数T表示方法所返回对象的类型
     * @return 返回T类型的对象
     */
    public <T> T single(String sql , RowHandler<T> handler , Object... params ){

        if( sql == null || ( sql = sql.trim() ).isEmpty() ) {
            throw new IllegalArgumentException( "被执行SQL不能为空" );
        }

        // 将整个SQL语句所有英文字母转大写
        sql = sql.toUpperCase() ;

        // 判断SQL语句是否不是DML语句
        if( !sql.startsWith( "SELECT" ) ){
            throw new RuntimeException( "对不起，老子只能执行SELECT语句" );
        }

        // 如果sql语句中包含参数占位符，则必须可变长参数必须有数据传入
        if( sql.indexOf( '?' ) != -1 && ( params == null || params.length == 0 ) ) {
            throw new IllegalArgumentException( "未设置参数占位符的值: " + sql );
        }

        T t = null ;
        // 创建 Statement 对象
        PreparedStatement ps = this.prepare( sql );
        // 设置参数占位符的值
        this.setParameters( ps , params );

        ResultSet rs = null;
        try {
            // 执行查询操作并获取结果集
            rs = ps.executeQuery();
            LOGGER.info( "查询成功" );
        } catch (SQLException e) {
            LOGGER.info( "查询失败: " + e.getMessage());
        }

        // 如果查询成功则不论查询结果是否包含数据ResultSet一定不是null
        if( rs != null ) {
            try {
                // 因为我们的查询至多返回一条数据，所以使用 if判断即可
                if (rs.next()) {
                    // 调用 RowHandler 处理结果集中的单行数据并返回T类型的对象
                    t = handler.handle(rs);
                }
            } catch ( SQLException e ) {
                LOGGER.info( "处理结果集时发生错误: " + e.getMessage() );
            }
        }

        // 释放资源
        this.release( rs );
        this.release( ps );

        return t ;
    }

    public <T> List<T> query(String sql , RowHandler<T> handler , Object... params ) {
        List<T> list = null ;
        PreparedStatement ps = this.prepare( sql );
        this.setParameters( ps , params );
        ResultSet rs = null;
        try {
            // 执行查询操作并获取结果集
            rs = ps.executeQuery();
            LOGGER.info( "查询成功" );
        } catch (SQLException e) {
            LOGGER.info( "查询失败: " + e.getMessage());
        }

        // 如果查询成功则不论查询结果是否包含数据ResultSet一定不是null
        if( rs != null ) {
            // 创建集合用于存放对象
            list = new ArrayList<>();
            try {
                while (rs.next()) {
                    // 使用 RowHandler 实现类的 handle 方法逐行处理结果集中的数据
                    T t = handler.handle(rs);
                    // 将包装好的对象添加到List集合中
                    list.add( t );
                }
            } catch ( SQLException e ) {
                LOGGER.info( "处理结果集时发生错误: " + e.getMessage() );
            }
        }

        // 释放资源
        this.release( rs );
        this.release( ps );

        return list ;
    }

    private void release( AutoCloseable ac ){
        if( ac != null ){
            try {
                ac.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 用来释放数据库连接的方法
     */
    public void release(){
        if( connection != null ){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}
